#pragma once

#include "log.hpp"
#include "sqlite3.h"
#include <any>
#include <string>
#include <unordered_map>
#include <vector>

#define sqlite_info(...)  info("sqlite", __VA_ARGS__)
#define sqlite_warn(...)  warn("sqlite", __VA_ARGS__)
#define sqlite_error(...) error("sqlite", __VA_ARGS__)

using ColName_t = std::string;                                 /* 列名称 */
using ColType_t = std::string;                                 /* 列类型 */
using TableName_t = const std::string &;                       /* 表名称 */
using PrimaryKey_t = const std::string &;                      /* 主键 */
using TableHead_t = std::unordered_map<ColName_t, ColType_t>;  /* 表头 */
using KeyValue_t = std::pair<ColName_t, std::any>;             /* 列名 + 值 */
using ColKeyValue_t = std::unordered_map<ColName_t, std::any>; /* 列名 + 值 */

class Sql {
private:
    std::string _dbPath;

private:
    class SqlGuard_t {
        sqlite3 **_db = nullptr;
        char **_errMessage = nullptr;

    public:
        SqlGuard_t(sqlite3 **db, char **errMessage);
        ~SqlGuard_t();
    };

private:
    std::string createTableCmd(TableName_t tableName, const TableHead_t &tableHead, PrimaryKey_t primaryKey);
    std::string dropTableCmd(TableName_t tableName);
    std::string insertTableCmd(TableName_t tableName, const ColKeyValue_t &keyVal);

public:
    Sql(const std::string &dbPath);

    bool createTable(TableName_t tableName, const TableHead_t &tableHead, PrimaryKey_t primaryKey);
    bool deleteTable(TableName_t tableName);
    bool insertTable(TableName_t tableName, const ColKeyValue_t &keyVal);
};

Sql::Sql(const std::string &dbPath)
    : _dbPath(dbPath)
{
}

Sql::SqlGuard_t::SqlGuard_t(sqlite3 **db, char **errMessage)
    : _db(db)
    , _errMessage(errMessage)
{
}

Sql::SqlGuard_t::~SqlGuard_t()
{
    if (*_errMessage) {
        sqlite_info("free err message");
        sqlite3_free(*_errMessage);
    }

    if (*_db) {
        sqlite_info("close db file");
        sqlite3_close(*_db);
    }
}

std::string Sql::createTableCmd(TableName_t tableName, const TableHead_t &tableHead, PrimaryKey_t primaryKey)
{
    std::stringstream cmd;
    cmd << "create table if not exists " << tableName << "(";

    for (const auto &item : tableHead) {
        cmd << item.first << " " << item.second << ", ";
    }

    cmd << "PRIMARY KEY (" << primaryKey << "));";

    return cmd.str();
}

std::string Sql::dropTableCmd(TableName_t tableName)
{
    std::stringstream cmd;
    cmd << "DROP TABLE " << tableName << ";";
    return cmd.str();
}

std::string Sql::insertTableCmd(TableName_t tableName, const ColKeyValue_t &keyVal)
{
    std::stringstream cmd;
    cmd << "INSERT INTO " << tableName;
    std::stringstream keys;
    std::stringstream values;
    keys << "(";
    values << "(";

    for (auto it = keyVal.begin(); it != keyVal.end(); ++it) {
        keys << it->first;

        if (it->second.has_value()) {
            if (it->second.type() == typeid(int)) {
                values << std::any_cast<int>(it->second);
            } else if (it->second.type() == typeid(double)) {
                values << std::any_cast<double>(it->second);
            } else if (it->second.type() == typeid(float)) {
                values << std::any_cast<float>(it->second);
            } else if (it->second.type() == typeid(uint32_t)) {
                values << std::any_cast<uint32_t>(it->second);
            } else if (it->second.type() == typeid(std::string)) {
                values << "'" << std::any_cast<std::string>(it->second) << "'";
            } else {
                sqlite_error("can't get values type, please check!!!");
            }
        }

        if (std::next(it) != keyVal.end()) {
            keys << " ,";
            values << " ,";
        }
    }
    keys << ")";
    values << ")";

    cmd << keys.str() << " VALUES" << values.str() << ";";

    return cmd.str();
}

bool Sql::createTable(TableName_t tableName, const TableHead_t &tableHead, PrimaryKey_t primaryKey)
{
    sqlite3 *db = nullptr;
    char *err = nullptr;
    SqlGuard_t guard(&db, &err);

    auto rc = sqlite3_open(_dbPath.c_str(), &db);
    if (rc != SQLITE_OK) {
        sqlite_error("sqlite open failed");
        return false;
    }

    auto cmd = createTableCmd(tableName, tableHead, primaryKey);

    rc = sqlite3_exec(db, cmd.c_str(), nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        sqlite_error("sqlite exec failed cmd --> {}, err --> {}", cmd, err);
        return false;
    }

    sqlite_info("sqlite exec success cmd --> {}", cmd);

    return true;
}

bool Sql::deleteTable(TableName_t tableName)
{
    sqlite3 *db = nullptr;
    char *err = nullptr;
    SqlGuard_t guard(&db, &err);

    auto rc = sqlite3_open(_dbPath.c_str(), &db);
    if (rc != SQLITE_OK) {
        sqlite_error("sqlite open failed");
        return false;
    }

    auto cmd = dropTableCmd(tableName);
    rc = sqlite3_exec(db, cmd.c_str(), nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        sqlite_error("sqlite exec failed cmd --> {}, err --> {}", cmd, err);
        return false;
    }

    sqlite_info("sqlite exec success cmd --> {}", cmd);

    return true;
}

bool Sql::insertTable(TableName_t tableName, const ColKeyValue_t &keyVal)
{
    sqlite3 *db = nullptr;
    char *err = nullptr;
    SqlGuard_t guard(&db, &err);

    auto rc = sqlite3_open(_dbPath.c_str(), &db);
    if (rc != SQLITE_OK) {
        sqlite_error("sqlite open failed");
        return false;
    }

    auto cmd = insertTableCmd(tableName, keyVal);
    rc = sqlite3_exec(db, cmd.c_str(), nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        sqlite_error("sqlite exec failed cmd --> {}, err --> {}", cmd, err);
        return false;
    }

    sqlite_info("sqlite exec success cmd --> {}", cmd);

    return true;
}
